﻿/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework
 * Version 2.10
 * 
 * Assembly Type Loader Class
 *      Provides static methods / functions for loading types from .NET assemblies.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using Adaptive.Foundation.Collections;

namespace Adaptive.Foundation.Reflection
{
    /// <summary>
    /// Provides static methods / functions for loading types from .NET assemblies.
    /// </summary>
    public static class AssemblyTypeLoader
    {
        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Gets a reference to the MSCORLIB system assembly currently in use.
        /// </summary>
        /// <returns>
        /// An <see cref="Assembly"/> instance for the main System namespace.
        /// </returns>
        public static Assembly GetMsCorLib()
        {
            return Assembly.GetAssembly(typeof(object));
        }
        /// <summary>
        /// Gets the location of mscorlib.dll.
        /// </summary>
        /// <returns>
        /// The absolute path in which the fundamental .NET base class library resides.
        /// </returns>
        public static string LocateMsCorlibPath()
        {
            Assembly systemObjectLibrary = null;            //The assembly from the mscorlib.dll file.
            string fileName = string.Empty;                 //The path name.
            string returnValue = string.Empty;              //The return value.
            
            //Load the base class library.
            systemObjectLibrary = Assembly.GetAssembly(typeof(object));

            //Extract the actual file name.
            fileName = systemObjectLibrary.CodeBase.Replace("file:///", string.Empty);
            returnValue = Path.GetDirectoryName(fileName);
            
            //Clear and return.
            fileName = null;
            systemObjectLibrary = null;
            return returnValue;
        }
        /// <summary>
        /// Gets the list of assembly DLLs in the current .NET Framework.
        /// </summary>
        /// <returns>
        /// A <see cref="T:List>"/> instance containing the loaded assemblies for 
        /// all the core DLLs in the current version of the .NET framework.
        /// </returns>
        /// <remarks>
        /// This method locates the absolute path of the current framework, such as,
        /// "\windows\microsoft.net\framework\v2.0.xxxx" and loads all the .NET DLLs that
        /// are located there.
        /// 
        /// This method does not load the content from the Global Assembly Cache (GAC).
        /// </remarks>
        public static List<Assembly> GetNetFrameworkDllList()
        {
            string[] files = null;                          //List of dlls.
            List<Assembly> assemblyList = null;             //List of assemblies.
            Assembly loadedAssembly = null;                 //A newly loaded assembly instance.
            Assembly systemObjectLibrary = null;            //The assembly from the mscorlib.dll file.

            //Load the base class library.
            systemObjectLibrary = Assembly.GetAssembly(typeof(object));

            //Get the list of DLLs in the same path as mscorlib.dll.
            files = Directory.GetFiles(LocateMsCorlibPath(), "*.dll");
            assemblyList = new List<Assembly>();

            //Attempt to load each item and insert into the list.
            foreach (string file in files)
            {
                //In case of load failure or the item is not actually an assembly...
                try
                {
                    loadedAssembly = Assembly.LoadFile(file);
                }
                catch
                {
                    loadedAssembly = null;
                }

                //Ensure the versions match.
                if ((loadedAssembly != null) && (loadedAssembly.ImageRuntimeVersion == systemObjectLibrary.ImageRuntimeVersion))
                    assemblyList.Add(loadedAssembly);
            }

            //Clear and return.
            Array.Clear(files, 0, files.Length);
            files = null;
            loadedAssembly = null;

            return assemblyList;
        }
        /// <summary>
        /// Gets a sorted type array from the specified assembly.
        /// </summary>
        /// <param name="assemblyToLoadFrom">
        /// An <see cref="Assembly"/> to read the data type list from.
        /// </param>
        /// <returns>
        /// A <see cref="Type"/> array containing the list of types in <i>assemblyToLoadFrom</i>,
        /// sorted alphabetically.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>assemblyToLoadFrom</i> parameter is <b>null</b>.
        /// </exception>
        public static Type[] GetSortedTypesList(Assembly assemblyToLoadFrom)
        {
            Type[] originalList = null;         //Original list of types.
            string[] keyList = null;            //List of type names as key values.
            int count = 0;                      //Iteration counter.
            int length = 0;                     //Iteration limit.

            if (assemblyToLoadFrom == null)
                throw new ArgumentNullException("assemblyToLoadFrom");

            //Load the types and copy the key name values.
            originalList = assemblyToLoadFrom.GetTypes();
            length = originalList.Length;
            keyList = new string[length];
            
            for (count = 0; count < length; count++)
                keyList[count] = originalList[count].FullName;

            //Sort the type array using the key names.
            Array.Sort(keyList, originalList);

            //Clear and return.
            Array.Clear(keyList, 0, keyList.Length);
            keyList = null;
            return originalList;
        }
        /// <summary>
        /// Loads the types from the supplied assembly, sorts the types by namespace and name, 
        /// and returns a sorted list of lists.
        /// </summary>
        /// <param name="assembly">
        /// The <see cref="Assembly"/> instance containing the types to be loaded.
        /// </param>
        /// <returns>
        /// An <see cref="OrderedDictionary"/> instance containing entries for each namespace,
        /// (using the namespace name as a key); for each namespace entry, a <see cref="T:List"/>
        /// is stored containing the sorted type list.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>assembly</i> parameter is <b>null</b>.
        /// </exception>
        public static OrderedDictionary SortIntoNamespaces(Assembly assembly)
        {
            Type[] sortedTypeList = null;           //The list of types in the assembly sorte by namespace and name.
            List<Type> typeListReference = null;    //New namespace type list.
            OrderedDictionary returnValue = null;   //Return alue.

            //Validate parameter.
            if (assembly == null)
                throw new ArgumentNullException("assembly");

            returnValue = new OrderedDictionary();

            //Get and sort the list of types.
            sortedTypeList = GetSortedTypesList(assembly);

            //For each type in the list, if the namespace is not yet in the return value, create
            // a new list entry for that namespace; else find the entry to that namespace and 
            // append the current type.
            foreach (Type item in sortedTypeList)
            {
                if ((item.Namespace != null) &&
                   (((item.IsPublic) && (!item.IsSpecialName) && (item.IsVisible))))
                {
                    if (returnValue[item.Namespace] == null)
                    {
                        //Create a new entry...
                        typeListReference = new List<Type>();
                        returnValue.Add(item.Namespace, typeListReference);
                    }
                    else
                        //Find namespace entry...
                        typeListReference = (List<Type>)returnValue[item.Namespace];

                    //Add the item to either result.
                    if (!typeListReference.Contains(item))
                        typeListReference.Add(item);
                }
            }
            return returnValue;
        }
        /// <summary>
        /// Creates a tree-style structure from the list of types according to 
        /// namespace assignment.
        /// </summary>
        /// <param name="assembly">
        /// The <see cref="Assembly"/> instance containing the types to be loaded.
        /// </param>
        /// <returns>
        /// An <see cref="Hashtable"/> instance containing entries for each namespace,
        /// (using the namespace name as a key); for each namespace entry, a <see cref="Hashtable"/>
        /// is stored containing the sorted type list.
        /// 
        /// Each namespace is seperated into its component name parts, and a tree structure
        /// is created; for example: System.Windows.Forms would result in a hashtable for "System"
        /// that contains a hashtable for "Windows" that contains a hashtable for "Forms" which
        /// also contains lists of the types explicitly defined in that namespace.
        /// 
        /// For each hash table, the namespace name is the key value, and the object is a 
        /// T:List containing the type definitions.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>assembly</i> parameter is <b>null</b>.
        /// </exception>
        public static Hashtable CreateTypeTree(Assembly assembly)
        {
            Type[] masterTypeList = null;                   //The master type list.
            Hashtable returnValue = null;                   //Return value.
            Hashtable treeNodePtr = null;                   //A "tree node" pointer to the namespace hash tables.
            List<Type> sublist = null;                      //The list of types to be contained in a hash table entry.
            string[] sortedNameList = null;                 //The list of sorted namespace names.
            string[] nameParts = null;                      //The parsed namespace name.
            int count = 0;                                  //Iteration counter.
            int length = 0;                                 //Iteration limit.            

            //Get the types and sort the namespaces.
            masterTypeList = assembly.GetTypes();
            sortedNameList = GetOrderedNamespaceList(masterTypeList);

            //Create the tree structure from the unique namespace(s) list.
            returnValue = new Hashtable();
            foreach (string namespaceName in sortedNameList)
            {
                //Seperate the dotted-notation of a namespace name into its component parts.
                nameParts = namespaceName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

                //Iterate through the tree, adding nodes as needed, for each namespace name part.
                treeNodePtr = returnValue;
                length = nameParts.Length;
                for (count = 0; count < length; count++)
                {
                    //If the namepart is not on the current level, then add a new hash table.
                    if (!treeNodePtr.ContainsKey(nameParts[count]))
                        treeNodePtr.Add(nameParts[count], new Hashtable());

                    //Move the pointer "down".
                    treeNodePtr = (Hashtable)treeNodePtr[nameParts[count]];
                }
                //Clear.
                Array.Clear(nameParts, 0, nameParts.Length);
            }

            //For each type instnce, locate the appropriate namespace-oriented hash table 
            //and append the type.
            foreach (Type exportedType in masterTypeList)
            {
                if ((exportedType.Namespace != null) && (exportedType.IsPublic))
                {
                    //Split the namespace parts.
                    nameParts = exportedType.FullName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

                    //Locate the appropriate hash table.
                    treeNodePtr = returnValue;
                    length = nameParts.Length;
                    for (count = 0; count < length - 1; count++)
                        treeNodePtr = (Hashtable)treeNodePtr[nameParts[count]];

                    //Create or locate the list of types.
                    sublist = (List<Type>)treeNodePtr["TypesList"];
                    if (sublist == null)
                    {
                        sublist = new List<Type>();
                        treeNodePtr.Add("TypesList", sublist);
                    }
                    sublist.Add(exportedType);
                }
            }

            //Clear and return.
            Array.Clear(masterTypeList, 0, masterTypeList.Length);
            Array.Clear(sortedNameList, 0, sortedNameList.Length);
            treeNodePtr = null;
            sortedNameList = null;
            masterTypeList = null;

            return returnValue;
        }
        /// <summary>
        /// Gets the list of unique namespaces from the list of types and 
        /// sorts them.
        /// </summary>
        /// <param name="listOfTypes">
        /// An array of <see cref="Type"/> instances whose namespace(s) are to 
        /// be extracted.
        /// </param>
        /// <returns>
        /// A string array containing the sorted name list.
        /// </returns>
        public static string[] GetOrderedNamespaceList(Type[] listOfTypes)
        {
            UniqueStringCollection namespaceNameList = null;        //List of names.
            string[] returnValue = null;                            //Return value.
            int count = 0;                                          //Iteration counter.
            int length = 0;                                         //Iteration limit.

            //Create the storage mechanism.
            length = listOfTypes.Length;
            returnValue = new string[length];
            namespaceNameList = new Adaptive.Foundation.Collections.UniqueStringCollection();

            //For each type, get the namespace and add it to the collection.
            //  If the namespace name value is already present, the collection will ignore it.
            for (count = 0; count < length; count++)
            {
                if ((listOfTypes[count].Namespace != null) && (listOfTypes[count].IsPublic))
                    namespaceNameList.Add(listOfTypes[count].Namespace);
            }

            //Sort the remaining values into an array.
            namespaceNameList.Sort();
            returnValue = namespaceNameList.ToArray();

            //Clear and return.
            namespaceNameList.Dispose();
            namespaceNameList = null;
            return returnValue;
        }
        #endregion

    }
}